이펙티드 타입스크립트 정리-1

중요한 것만 뽑기

2023-09-09

코드 생성과 타입은 관계가 없다!

타입스크립트 컴파일러는 크게 2가지 역할을 수행합니다.

이 두가지는 완전히 독립적입니다. 다시 말해, 타입스크립트 -> 자바스크립트로 변활할 때, 코드 내의 타입에는 영향을 주지 않고, 자바스크립트의 시점에도 타입은 영향을 미치지 않습니다.

타입 오류가 있는 코드도 컴파일은 동작합니다.

컴파일은 타입체크와 완전히 독립적으로 동작하기 때문에, 타입오류가 있는 코드도 컴파일이 가능합니다.

런타임 시점에는 타입 체크가 불가능합니다.

interface Square{
    width:number;
}

interface Rectangle extends Square{
    height:number;
}

type Shape = Square | Rectangle

function calculateArea(shape : Shape){
    if(shape instanceof Rectangle){
        // Rectangle는 형식만 참조하지만, 여기선 값으로 사용되고 있습니다.
        return shape.width * shape.height;

        //Shape형식에 height속성이 없습니다.
    }
    else{
        return shape.width * shape.width;
    }
}

instanceof 체크는 런타임에 일어나지만, Rectangle은 타입이기 때문에 런타임 시점에 아무 역할을 할 수 없습니다. 타입스크립트 타입은 "제거 가능"합니다. 실제로 자바스크립트로 컴파일되는 시점에 모든 타입, 인터페이스 구문은 제거됩니다.

앞서 코드를 다음과 같이 바꾸면 런타임에서 정보를 유지할 수 있습니다.

function calculateArea(shape : Shape){
    if('height' in shape){
        shape; //타입이 Rectangle
        return shape.width * shape.height;
    }
    else{
        shape; //타입이 Square
        return shape.width * shape.width;
    }
}

다른 방법으로는 런타임에 타입 정보를 알 수 있게 하면 됩니다.

interface Square{
    kind : 'square';
    width: number;
}

interface Rectangle{
    kind : 'rectangle';
    height : number;
    width : number;
};

type Shape = Square | Rectangle;

function calculateArea(shape : Shape){
    if(shape.kind === 'rectangle'){
        shape; //타입이 Rectangle
        return shape.width * shape.height;
    }
    else{
        shape; //타입이 Square
        return shape.width * shape.width;
    }
}

타입 연산은 런타임에 영향을 주지 않는다.

먼저 아래의 코드가 있습니다.

function asNumber(val:number | string) : number {
    return val as number;
}

이 코드는 다음과 같이 변환됩니다.

function asNumber(val){
    return val;
}

보다시피 아무런 정제과정이 존재하지 않습니다. as number는 타입연산이고, 런타임에는 아무 영향을 끼치지 않습니다. 따라서 런타임의 타입을 자바스크립트로 체크 해야 합니다.

function asNumber(val : number | string) : number {
    return typeof(val) === 'string' ? Number(val) : val;
}

any타입 지양하기

타입 체커를 any타입으로 헤제할 수 있습니다.

let age : number;
age = '12';

// '12형식은 number 형식에 할당할 수 없습니다.'
age = '12' as any; //OK

as any로 오류를 해결할 수 있었습니다. 그러나 일부 특별한 경우를 제외하고 any를 사용하면 타입스크립트의 장점을 누릴 수 없게 됩니다.

any타입은 함수 시그니처를 무시합니다.

function calculateAge(birthDay : Date) : number {
    //
}

let birthday : any = '2020-02-26';

calculateAge(birthday); //정상

매개변수가 Date타입이여 하지만 any를 집어넣으면 이상없이 동작합니다. 이런 경우 종종 문제가 될 수 있습니다.

any 타입은 자동완성 기능을 사용할 수 없습니다.

any타입은 또한 리펙토링 시 버그를 감춰버립니다.

interface ComponentProps {
    onSelectItem : (item : any) => void;
}

function renderSelector(props : ComponentProps){

}

let selectedId : number = 0;
function handleSelectItem(item : any) {
    selectedId = item.id;
}

renderSelector({onSelectItem : handleSelectItem})

handleSelectItem은 id만 필요하기 때문에 ComponentProps를 변경해보았습니다.

interface ComponentProps {
    onSelectItem: (id : number) => void
}

handleSelectItem은 any의 매개변수를 받아 id를 전달받아도 문제 없다고 나옵니다. id를 전달받으면 타입 체커는 통과하지만, 런타임에서 오류가 발생합니다. 구체적인 타입을 사용해야 합니다.

타입 체커가 실수를 잡아주고 코드의 신뢰도를 높여주게 됩니다. 하지만 any 타입을 사용하게 되어, 런타임에 타입 오류를 발견하게 된다면, 타입 체커를 신뢰할 수 없게 됩니다.